using System;
using System.Collections;
using System.Configuration;
using System.Xml;
using System.Xml.XPath;

namespace Pegasus.Configuration
{
	/// <summary>
	/// Allows extension of any configuration section that is accessed using ConfigurationSettings.GetConfig 
	/// to enable mutiple types and tables/list to be held in the same file
	/// </summary>
	/// <example>
	/// <code>
	///&lt;configuration&gt;
	///    &lt;configSections&gt;
	///        &lt;section name="mySpecialSection" type="Pegasus.Configuration.ConfigurationSettingsCollections, Pegasus.Library"/&gt;
	///    &lt;/configSections&gt;
	/// 
	///    &lt;mySpecialSection&gt;
	///			&lt;element type="Hashtable"&gt;
	///				&lt;element key="test0"&gt;string value one&lt;/element&gt;
	///				&lt;element key="test1" type="Int32"&gt;1&lt;/element&gt;
	///				&lt;element key="test2" type="Double"&gt;2.5&lt;/element&gt;
	///				&lt;element key="test3" type="string"&gt;string value two&lt;/element&gt;
	///				&lt;element key="test4" type="Hashtable"&gt;
	///					&lt;element key="test00"&gt;string value one&lt;/element&gt;
	///					&lt;element key="test01" type="Int32"&gt;1&lt;/element&gt;
	///				&lt;/element&gt;
	///				&lt;element key="test5" type="ArrayList"&gt;
	///					&lt;element&gt;string value one&lt;/element&gt;
	///					&lt;element type="Int32"&gt;1&lt;/element&gt;
	///				&lt;/element&gt;
	///			&lt;/element&gt;
	///    &lt;/mySpecialSection&gt;
	/// 
	///</code>
	/// This config file would be parsed and presented to the application 
	/// using the ConfigurationSettings.GetConfig() method to access the top most element
	/// <code>
	///		Hashtable table = (Hashtable) ConfigurationSettings.GetConfig( "mySpecialSection" );
	/// </code>
	/// Subelements of a Hashtable element are required to have the key attribute.  
	/// Element tags that do not have a type specified are processed as strings.
	/// The following are a list of the valid types supported.
	/// 
	/// basic types
	///		string (default)
	///		char
	///		bool or boolean
	///		byte
	///		short
	///		int16
	///		int32
	///		int64
	///		float or single
	///		double
	///		decimal
	///		
	///	complex types
	///		datetime
	///		guid
	///	
	///	collections
	///		hashtable
	///		arraylist
	/// </example>
	public class ConfigurationSettingsCollections : IConfigurationSectionHandler
	{
		/// <summary>
		/// Default Constructor
		/// </summary>
		public ConfigurationSettingsCollections()
		{
		}

		/// <summary>
		///	Parses an <c>XmlNode</c> from a configuration file to produce an XmlNode that is compatible with the original 
		///	<c>IConfigurationSectionHander</c> for the section. Instantiates and instance of the original handler and passes in 
		///	the new <c>XmlNode</c> to get back an <c>object</c> of the same type as the original handler would have returned.
		/// </summary>
		/// <param name="parent"></param>
		/// <param name="configContext"></param>
		/// <param name="sectionNode">The <c>XmlNode</c> from the config file</param>
		/// <returns>
		///	<c>object</c> of the same type as the original <c>IConfigurationSectionHandler</c> returns
		///	</returns>
		public virtual object Create( object parent, object configContext, XmlNode sectionNode )
		{
			XPathNavigator sectionNav = sectionNode.CreateNavigator();
			XPathNodeIterator childIter = sectionNav.SelectChildren( "", "" );
			
			// Only do the first element even if there is more than one
			if(  childIter.MoveNext() )
			{
				return BuildElement( childIter.Current );
			}

			return null;
		}

		/// <summary>
		/// Build as
		/// </summary>
		/// <param name="elementNav"></param>
		/// <returns></returns>
		private object BuildElement( XPathNavigator elementNav )
		{
			object ret = null;

			// Get the type attribute
			string type = elementNav.GetAttribute( "type", "" );
			if( type == null || type.Length == 0 )
			{
				type = "string";
			}

			string value = elementNav.GetAttribute( "value", "" );

			switch( type.ToLower() )
			{
				case "string":
					ret = value;
					break;

				case "char":
					ret = Convert.ToChar( value );
					break;

				case "bool":
				case "boolean":
					ret = Convert.ToBoolean( value );
					break;

				case "byte":
					ret = Convert.ToByte( value );
					break;

				case "short":
				case "int16":
					ret = Convert.ToInt16( value );
					break;

				case "int32":
					ret = Convert.ToInt32( value );
					break;

				case "int64":
					ret = Convert.ToInt64( value );
					break;

				case "float":
				case "single":
					ret = Convert.ToSingle( value );
					break;

				case "double":
					ret = Convert.ToDouble( value );
					break;

				case "decimal":
					ret = Convert.ToDecimal( value );
					break;

				case "datetime":
					ret = Convert.ToDateTime( value );
					break;
					
				case "guid":
					ret = new Guid( value );
					break;

				case "hashtable":
					ret = BuildHashtable( elementNav );
					break;

				case "arraylist":
					ret = BuildArrayList( elementNav );
					break;
			}

			return ret;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="tableNav"></param>
		/// <returns></returns>
		private Hashtable BuildHashtable( XPathNavigator tableNav )
		{
			Hashtable retTable = new Hashtable();

			XPathNodeIterator elementsIter = tableNav.SelectChildren( "", "" );
			while( elementsIter.MoveNext() )
			{
				XPathNavigator elementNav = elementsIter.Current;
				
				// Get the key attribute
				string key = elementNav.GetAttribute( "key", "" );
				if( key != null && key.Length > 0 )
				{
					object element = BuildElement( elementNav );
					retTable.Add( key, element );
				}
				else
				{
					throw new ConfigurationErrorsException( "ConfigurationSettingsCollections: Hashtable element with out a key attribute" );
				}
			}

			return retTable;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="listNav"></param>
		/// <returns></returns>
		private ArrayList BuildArrayList( XPathNavigator listNav )
		{
			ArrayList retList = new ArrayList();

			XPathNodeIterator elementsIter = listNav.SelectChildren( "", "" );
			while( elementsIter.MoveNext() )
			{
				retList.Add( BuildElement( elementsIter.Current ) );
			}

			return retList;
		}
	}
}
